/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cm_model.cpp - models format loading
//


#include "cm_local.h"


#define CLIP_MODELS_HASH_SIZE			(MAX_CLIP_MODELS >> 2)

static clipModel_t *		cm_defaultModel;

static glqPlane *			cm_boxPlanes;

static clipInlineModel_t *	cm_boxInlineModel;
static clipModel_t *		cm_boxModel;

static clipModel_t *		cm_modelsHashTable[CLIP_MODELS_HASH_SIZE];
static clipModel_t *		cm_models[MAX_CLIP_MODELS];
static int					cm_numModels;


/*
 ==============================================================================

 MODEL LOADING

 ==============================================================================
*/


/*
 ==================
 CM_LoadModel
 ==================
*/
static clipModel_t *CM_LoadModel (const char *name, bool defaulted, clipModelType_t type, void *data, int size){

	clipModel_t	*model;
	uint		hashKey;

	if (cm_numModels == MAX_CLIP_MODELS)
		Com_Error(false, "CM_LoadModel: MAX_CLIP_MODELS hit");

	cm_models[cm_numModels++] = model = (clipModel_t *)Mem_Alloc(sizeof(clipModel_t), TAG_COLLISION);

	// Fill it in
	Str_Copy(model->name, name, sizeof(model->name));
	model->defaulted = defaulted;
	model->type = type;
	model->data = data;
	model->size = size;

	// Add to hash table
	hashKey = Str_HashKey(model->name, CLIP_MODELS_HASH_SIZE, false);

	model->nextHash = cm_modelsHashTable[hashKey];
	cm_modelsHashTable[hashKey] = model;

	return model;
}

/*
 ==================
 CM_FindModel
 ==================
*/
clipModel_t *CM_FindModel (const char *name){

	clipModel_t	*model;
	uint		hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, CLIP_MODELS_HASH_SIZE, false);

	for (model = cm_modelsHashTable[hashKey]; model; model = model->nextHash){
		if (!Str_ICompare(model->name, name))
			return model;
	}

	// TODO: do we load .cm models here later for physics stuff?

	// Not found
	return NULL;
}

/*
 ==================
 CM_RegisterModel
 ==================
*/
clipModel_t *CM_RegisterModel (const char *name){

	clipModel_t	*model;

	if (!cm.loaded)
		Com_Error(false, "CM_RegisterModel: map not loaded");

	model = CM_FindModel(name);
	if (model)
		return model;

	// Register the name even if not found
	Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find clip model '%s', using default\n", name);

	return CM_LoadModel(name, true, cm_defaultModel->type, cm_defaultModel->data, 0);
}


// ============================================================================


/*
 ==================
 CM_LoadInlineModel

 Used by the BSP loading code for inline models
 ==================
*/
void CM_LoadInlineModel (int index, void *data){

	char	name[32];

	if (index == 0){
		CM_LoadModel("_worldMap", false, CLIP_MODEL_INLINE, data, 0);
		return;
	}

	Str_SPrintf(name, sizeof(name), "_inlineModel%i", index);

	CM_LoadModel(name, false, CLIP_MODEL_INLINE, data, 0);
}

/*
 ==================
 CM_GetModelBounds
 ==================
*/
void CM_GetModelBounds (clipModel_t *model, glqBounds &bounds){

	if (!cm.loaded)
		Com_Error(false, "CM_GetModelBounds: map not loaded");

	switch (model->type){
	case CLIP_MODEL_INLINE:
		bounds = ((clipInlineModel_t *)model->data)->bounds;
		break;
	default:
		Com_Error(false, "CM_GetModelBounds: bad model type (%i)", model->type);
	}
}

/*
 ==================
 
 NOTE: this will create a inline box model
 ==================
*/
void CM_CreateBoxModel (){

	int		size;
	int		i;

	// Allocate the model
	cm_boxInlineModel = (clipInlineModel_t *)Mem_Alloc(sizeof(clipInlineModel_t), TAG_COLLISION);
	size = sizeof(clipInlineModel_t);

	// Create the planes
	cm_boxPlanes = (glqPlane *)Mem_Alloc(6 * sizeof(glqPlane), TAG_COLLISION);
	size += 6 * sizeof(glqPlane);

	for (i = 0; i < 6; i++){
		cm_boxPlanes[i].Normal().Clear();

		if (i & 1)
			cm_boxPlanes[i].Normal()[i>>1] = -1.0f;
		else
			cm_boxPlanes[i].Normal()[i>>1] = 1.0f;

		cm_boxPlanes[i].SetDist(8.0f);
		cm_boxPlanes[i].SetSignBits();
	}

	// Set the bounds
	cm_boxInlineModel->bounds.Set(-8.0f, 8.0f);

	// Load the model
	cm_boxModel = CM_LoadModel("_box", false, CLIP_MODEL_INLINE, cm_boxInlineModel, size);
}

/*
 ==================
 
 NOTE: this will use the created inline box model and just fill it in
 
 To keep everything totally uniform, bounding boxes are turned into inline
 models instead of being compared directly
 ==================
*/
clipModel_t *CM_SetupBoxModel (int contents, const glqBounds &bounds){

	if (!cm.loaded)
		Com_Error(false, "CM_SetupBoxModel: map not loaded");

	// Set up the planes
	cm_boxPlanes[0].SetDist( bounds[1][0]);
	cm_boxPlanes[1].SetDist(-bounds[0][0]);
	cm_boxPlanes[2].SetDist( bounds[1][1]);
	cm_boxPlanes[3].SetDist(-bounds[0][1]);
	cm_boxPlanes[4].SetDist( bounds[1][2]);
	cm_boxPlanes[5].SetDist(-bounds[0][2]);

	return cm_boxModel;
}

/*
 ==================
 CM_ClearModels
 ==================
*/
void CM_ClearModels (){

	// Clear model list
	Mem_Fill(cm_modelsHashTable, 0, sizeof(cm_modelsHashTable));
	Mem_Fill(cm_models, 0, sizeof(cm_models));

	cm_numModels = 0;
}

/*
 ==================
 CM_EntityString
 ==================
*/
char *CM_EntityString (){

	if (!cm.loaded)
		return "";

	if (!cm.numEntityChars)
		return "";

	return cm.entityString;
}


/*
 ==============================================================================

 CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 
 TODO: print the clip map information
 ==================
*/
static void CM_ClipMapInfo_f (){

	if (!cm.loaded){
		Com_Printf("Clip map not loaded\n");
		return;
	}

	Com_Printf("\n");
	Com_Printf("%s\n", cm.name);
	Com_Printf("----------------------------------------\n");

	Com_Printf("%5i KB in %8i entity characters\n", SIZE_KB(cm.numEntityChars * sizeof(char)), cm.numEntityChars);
	Com_Printf("%5i KB in %8i inline models\n", SIZE_KB(cm.numInlineModels * sizeof(clipInlineModel_t)), cm.numInlineModels);

	Com_Printf("----------------------------------------\n");
	Com_Printf("%.2f MB of clip map data\n", SIZE_MB_FLOAT(cm.size));
	Com_Printf("\n");
}

/*
 ==================
 
 TODO: print the clip model information
 ==================
*/
static void CM_ListClipModels_f (){

	clipModel_t			*model;
	clipInlineModel_t	*inlineModel;
	int					bytes = 0;
	int					i;

	if (!cm.loaded){
		Com_Printf("Clip map not loaded\n");
		return;
	}

	Com_Printf("\n");
	Com_Printf("      -bshs- -srfs- -pnts--- -plns--- -fcts--- -size- -name-----------\n");

	for (i = 0; i < cm_numModels; i++){
		model = cm_models[i];

		bytes += model->size;

		Com_Printf("%4i: ", i);

		switch (model->type){
		case CLIP_MODEL_INLINE:

			break;
		default:
			Com_Printf("?????? ?????? ???????? ???????? ???????? ");

			break;
		}

		Com_Printf("%5ik ", SIZE_KB(model->size));

		Com_Printf("%s%s\n", model->name, (model->defaulted) ? " (DEFAULTED)" : "");
	}

	Com_Printf("----------------------------------------------------------------------\n");
	Com_Printf("%i total clip models\n", cm_numModels);
	Com_Printf("%.2f MB of clip model data\n", SIZE_MB_FLOAT(bytes));
	Com_Printf("\n");
}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 CM_InitModels
 ==================
*/
void CM_InitModels (){

	// Add commands
	Cmd_AddCommand("clipMapInfo", CM_ClipMapInfo_f, "Shows clip map information", NULL);
	Cmd_AddCommand("listClipModels", CM_ListClipModels_f, "Lists loaded clip models", NULL);
}

/*
 ==================
 CM_ShutdownModels
 ==================
*/
void CM_ShutdownModels (){

	// Remove commands
	Cmd_RemoveCommand("clipMapInfo");
	Cmd_RemoveCommand("listClipModels");

	// Clear model list
	Mem_Fill(cm_modelsHashTable, 0, sizeof(cm_modelsHashTable));
	Mem_Fill(cm_models, 0, sizeof(cm_models));

	cm_numModels = 0;
}